<?php declare(strict_types=1);
/**
 * @author      xianganyall <xianganyall@gmail.com>
 * @copyright   2023-2025 owner
 **/

namespace Srv\Libs\Plugins\Qc;

use Exception;
use Qcloud\Cos\Client;
use QCloud\COSSTS\Scope;
use QCloud\COSSTS\Sts;
use Srv\Conf\ConfModel\StorageConf;
use Srv\Libs\Common\CommTime;

class QcCos
{
    private ?StorageConf $StorageConf       = null;     // StorageConf
    private ?Client $CosClient              = null;     // Client

    /**
     * @param StorageConf $StorageConf
     * __construct
     */
    public function __construct(StorageConf $StorageConf)
    {
        $this->StorageConf      = $StorageConf;
    }

    /**
     * @param string $path
     * @param int $expireTime
     * @return array
     * 获取单个文件上传临时Key
     */
    public function getTempKeys(string $path, int $expireTime = 3600):array
    {
        $tmpKeyData     = [];
        if(strlen($path) < 1) return $tmpKeyData;
        $expireTime     = min(max($expireTime, 0), 86400);
        $allowActions   = [     // 密钥的权限列表。简单上传和分片需要以下的权限，其他权限列表请看 https://cloud.tencent.com/document/product/436/31923
            // 简单上传
            'name/cos:PutObject',
            'name/cos:PostObject',
            // 分片上传
            'name/cos:InitiateMultipartUpload',
            'name/cos:ListMultipartUploads',
            'name/cos:ListParts',
            'name/cos:UploadPart',
            'name/cos:CompleteMultipartUpload',
            'name/cos:AbortMultipartUpload',
        ];
        $bucket         = $this->StorageConf->getAppName().'-'.$this->StorageConf->getAppId();
        $region         = $this->StorageConf->getPoint();
        $config         = [
            'url'               => 'https://sts.tencentcloudapi.com/',
            'domain'            => 'sts.tencentcloudapi.com',
            'proxy'             => '',
            'secretId'          => $this->StorageConf->getSecretId(),   // 密钥
            'secretKey'         => $this->StorageConf->getSecretKey(),  // 密钥
            'bucket'            => $bucket,                             // bucket-100000000
            'region'            => $region,                             // 所在区 https://cloud.tencent.com/document/product/436/6224
            'durationSeconds'   => $expireTime,                         // 有效期
            'allowPrefix'       => [ltrim($path, '/')],                 // 允许的路径前缀 https://github.com/tencentyun/qcloud-cos-sts-sdk/blob/master/php/demo/sts_internal_test.php
            'allowActions'      => $allowActions,                       // 允许的操作 https://cloud.tencent.com/document/product/436/31923
            'condition'         => [],                                  // 允许的生效条件 https://cloud.tencent.com/document/product/436/71306
        ];
        try{
            $Sts            = new Sts();
            $tempKeys       = $Sts->getTempKeys($config);
        }catch(Exception $Exception){
            return $tmpKeyData;
        }
        if(!isset($tempKeys['expiredTime']) || !isset($tempKeys['startTime']) || !isset($tempKeys['credentials'])) return $tmpKeyData;
        return [
            'tmpSecretId'       => strval($tempKeys['credentials']['tmpSecretId']??''),
            'tmpSecretKey'      => strval($tempKeys['credentials']['tmpSecretKey']??''),
            'sessionToken'      => strval($tempKeys['credentials']['sessionToken']??''),
            'expireTime'        => intval($tempKeys['expiredTime']),
            'startTime'         => intval($tempKeys['startTime']),
        ];
    }

    /**
     * @param array $pathList
     * @param int $expireTime
     * @return array
     * 获取多个文件上传临时Key
     */
    public function getTempKeysMulti(array $pathList, int $expireTime = 3600):array
    {
        $tmpKeyData     = [];
        if(count($pathList) < 1) return $tmpKeyData;
        $expireTime     = min(max($expireTime, 0), 7200);
        $allowActions   = [     // 密钥的权限列表。简单上传和分片需要以下的权限，其他权限列表请看 https://cloud.tencent.com/document/product/436/31923
            // 简单上传
            'name/cos:PutObject',
            'name/cos:PostObject',
            // 分片上传
            'name/cos:InitiateMultipartUpload',
            'name/cos:ListMultipartUploads',
            'name/cos:ListParts',
            'name/cos:UploadPart',
            'name/cos:CompleteMultipartUpload',
            'name/cos:AbortMultipartUpload',
        ];
        $bucket         = $this->StorageConf->getAppName().'-'.$this->StorageConf->getAppId();
        $region         = $this->StorageConf->getPoint();
        $scopes         = [];
        foreach($pathList as $path) if(strlen($path) > 0) $scopes[] = new Scope($allowActions, $bucket, $region, $path);
        if(count($scopes) < 1) return $tmpKeyData;
        $policy         = $this->getPolicyData($scopes);
        if(count($policy) < 1) return $tmpKeyData;
        $config         = [
            'url'               => 'https://sts.tencentcloudapi.com/',
            'domain'            => 'sts.tencentcloudapi.com',
            'proxy'             => '',
            'secretId'          => $this->StorageConf->getSecretId(),   // 密钥
            'secretKey'         => $this->StorageConf->getSecretKey(),  // 密钥
            'bucket'            => $this->StorageConf->getAppName().'-'.$this->StorageConf->getAppId(), // bucket-125000000
            'region'            => $this->StorageConf->getPoint(),      // 所在区
            'durationSeconds'   => $expireTime,                         // 有效期
            'policy'            => $policy,
        ];
        try{
            $Sts            = new Sts();
            $tempKeys       = $Sts->getTempKeys($config);
        }catch(Exception $Exception){
            return $tmpKeyData;
        }
        if(!isset($tempKeys['expiredTime']) || !isset($tempKeys['startTime']) || !isset($tempKeys['credentials'])) return $tmpKeyData;
        return [
            'tmpSecretId'       => strval($tempKeys['credentials']['tmpSecretId']??''),
            'tmpSecretKey'      => strval($tempKeys['credentials']['tmpSecretKey']??''),
            'sessionToken'      => strval($tempKeys['credentials']['sessionToken']??''),
            'expireTime'        => intval($tempKeys['expiredTime']),
            'startTime'         => intval($tempKeys['startTime']),
        ];
    }

    /**
     * @param array $scopes
     * @return array
     * 获取策略数据 https://cloud.tencent.com/document/product/436/31923#policy
     */
    private function getPolicyData(array $scopes):array
    {
        $scopesSize     = count($scopes);
        if($scopesSize < 1) return [];
        $statements     = [];
        foreach($scopes as $scope){
            if($scope instanceof Scope){
                try{
                    $actions    = $scope->get_action();
                    $resource   = $scope->get_resource();
                }catch(Exception $Exception){
                    return [];
                }
                $statements[]   = [
                    'action'        => $actions,
                    'effect'        => $scope->get_effect(),
                    'resource'      => $resource,
                ];
            }
        }
        if(count($statements) < 1) return [];
        return ['version' => '2.0', 'statement' => $statements];
    }

    /**
     * @return Client
     * 获取COS-Client
     */
    private function getCosClient():Client
    {
        if($this->CosClient instanceof Client) return $this->CosClient;
        $this->CosClient    = new Client(['schema' => 'https', 'region' => $this->StorageConf->getPoint(), 'credentials' => ['secretId' => $this->StorageConf->getSecretId(), 'secretKey' => $this->StorageConf->getSecretKey()], 'signHost' => false]);
        return $this->CosClient;
    }

    /**
     * @param string $remote
     * @param string $local
     * @return bool
     * 上传文件
     */
    public function uploadFile(string $remote, string $local):bool
    {
        if(strlen($remote) < 2 || strlen($local) < 2 || !file_exists($local)) return false;
        try{
            $fpLocal    = fopen($local, 'rb');
            $bucket     = $this->StorageConf->getAppName().'-'.$this->StorageConf->getAppId();
            $result     = $this->getCosClient()->upload($bucket, $remote, $fpLocal);
        }catch(Exception $Exception){
            return false;
        }
        return (bool)$result;
    }

    /**
     * @param string $remote
     * @param string $local
     * @return bool
     * 下载文件
     */
    public function downloadFile(string $remote, string $local):bool
    {
        if(strlen($remote) < 2 || strlen($local) < 2 || file_exists($local)) return false;
        try{
            $bucket     = $this->StorageConf->getAppName().'-'.$this->StorageConf->getAppId();
            $result     = $this->getCosClient()->download($bucket, $remote, $local);
        }catch(Exception $Exception){
            return false;
        }
        return (bool)$result;
    }

    /**
     * @param string $remote
     * @return bool
     * 检查文件是否存在
     */
    public function existsFile(string $remote):bool
    {
        if(strlen($remote) < 2) return false;
        try{
            $bucket     = $this->StorageConf->getAppName().'-'.$this->StorageConf->getAppId();
            $result     = $this->getCosClient()->doesObjectExist($bucket, $remote);
        }catch(Exception $Exception){
            return false;
        }
        return $result;
    }

    /**
     * @param string $remote
     * @return bool
     * 删除文件
     */
    public function deleteFile(string $remote):bool
    {
        if(strlen($remote) < 2) return false;
        try{
            $bucket     = $this->StorageConf->getAppName().'-'.$this->StorageConf->getAppId();
            $result     = $this->getCosClient()->DeleteObject(['Bucket' => $bucket, 'Key' => $remote]);
        }catch(Exception $Exception){
            return false;
        }
        return boolval($result);
    }

    /**
     * @param string $remote
     * @param string $expires
     * @return string
     * 生成访问的签名Query
     */
    public function genPreSignQuery(string $remote, string $expires = '+30 minutes'):string
    {
        if(strlen($remote) < 2) return '';
        try{
            $bucket     = $this->StorageConf->getAppName().'-'.$this->StorageConf->getAppId();
            $Uri        = $this->getCosClient()->getPresignedUrl('GetObject', ['Bucket' => $bucket, 'Key' => ltrim($remote, '/')], $expires);
            return urldecode(substr($Uri->getQuery(), 5, -1));
        }catch(Exception $Exception){
            $currTime   = CommTime::getTimeStamp();
            $startTime  = $currTime - 60;
            $expireTime = strtotime($expires);
            if($expireTime === false || $expireTime <= $startTime) $expireTime = $startTime + 1800;
            $signTime   = $startTime.';'.$expireTime;
            $signKey    = hash_hmac('sha1', $signTime, $this->StorageConf->getSecretKey());
            $signature  = hash_hmac('sha1', "sha1\n".$signTime."\n".sha1("get\n".$remote."\n\n\n")."\n", $signKey);
            return 'q-sign-algorithm=sha1&q-ak='.$this->StorageConf->getSecretId().'&q-sign-time='.$signTime.'&q-key-time='.$signTime.'&q-header-list=&q-url-param-list=&q-signature='.$signature;
        }
    }
}